/*
 * Copyright (c) 2001 Jani Kajala
 *
 * Permission to use, copy, modify, distribute and sell this
 * software and its documentation for any purpose is hereby
 * granted without fee, provided that the above copyright notice
 * appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation.
 * Jani Kajala makes no representations about the suitability
 * of this software for any purpose. It is provided "as is"
 * without express or implied warranty.
 */

#include "TextFile.h"
#include <cstdio>
#include <cctype>

class TextFile::TextFileImpl
{
	public:
		TextFile::ErrorType		err;
		int						line;

		explicit TextFileImpl(const char* filename)
		{
			err				= TextFile::ERROR_NONE;
			line			= 1;
			m_peeked		= false;
			m_peekedChar	= 0;
			m_file			= fopen(filename, "rt");

			if(!m_file)
				err = TextFile::ERROR_OPEN;
		}

		~TextFileImpl()
		{
			if(m_file)
			{
				fclose(m_file);
				m_file = 0;
			}
		}

		bool eof() const
		{
			if(err)
				return true;
			return 0 != feof(m_file);
		}

		bool peekChar(char* ch)
		{
			if(err)
				return false;

			if(!m_peeked)
			{
				int c = getc(m_file);
				if(EOF != c)
				{
					m_peeked = true;
					m_peekedChar = (char)c;
				}
				else
				{
					if(ferror(m_file))
						err = TextFile::ERROR_READ;
				}
			}

			if(m_peeked)
				*ch = m_peekedChar;
			return m_peeked;
		}

		bool readChar(char* ch)
		{
			if(err)
				return false;

			bool more = peekChar(ch);
			m_peeked = false;
			if(more && *ch == '\n')
				++line;
			return more;
		}

		bool skipWhitespace()
		{
			if(err)
				return false;

			char ch;
			while(peekChar(&ch))
			{
				if(!isspace(ch))
					break;
				readChar(&ch);
			}
			return !eof();
		}

		bool readString(char* buf, int size)
		{
			if(err)
				return false;

			skipWhitespace();

			int count = 0;
			char ch;
			while(peekChar(&ch))
			{
				if(isspace(ch))
					break;
				if(count + 1 < size)
					buf[count++] = ch;
				readChar(&ch);
			}
			if(size > 0)
				buf[count] = 0;
			return count > 0;
		}

		void skipLine()
		{
			if(err)
				return;

			char ch;
			while(readChar(&ch))
			{
				if(ch == '\n')
					break;
			}
		}

		long readHex()
		{
			if(err)
				return 0;

			skipWhitespace();

			// hex must start with alphanumeric character
			char ch;
			if(!peekChar(&ch) || !isalnum(ch))
			{
				err = TextFile::ERROR_PARSE;
				return 0;
			}

			long x = 0;
			while(peekChar(&ch))
			{
				switch(ch)
				{
					case '0':
						x <<= 4;
						x += 0;
						break;
					case '1':
						x <<= 4;
						x += 1;
						break;
					case '2':
						x <<= 4;
						x += 2;
						break;
					case '3':
						x <<= 4;
						x += 3;
						break;
					case '4':
						x <<= 4;
						x += 4;
						break;
					case '5':
						x <<= 4;
						x += 5;
						break;
					case '6':
						x <<= 4;
						x += 6;
						break;
					case '7':
						x <<= 4;
						x += 7;
						break;
					case '8':
						x <<= 4;
						x += 8;
						break;
					case '9':
						x <<= 4;
						x += 9;
						break;
					case 'a':
					case 'A':
						x <<= 4;
						x += 0xA;
						break;
					case 'b':
					case 'B':
						x <<= 4;
						x += 0xB;
						break;
					case 'c':
					case 'C':
						x <<= 4;
						x += 0xC;
						break;
					case 'd':
					case 'D':
						x <<= 4;
						x += 0xD;
						break;
					case 'e':
					case 'E':
						x <<= 4;
						x += 0xE;
						break;
					case 'f':
					case 'F':
						x <<= 4;
						x += 0xF;
						break;
					default:
						return x;
				}
				readChar(&ch);
			}
			return x;
		}

	private:
		bool	m_peeked;
		char	m_peekedChar;
		FILE*	m_file;

		TextFileImpl(const TextFileImpl &);
		TextFileImpl & operator=(const TextFileImpl &);
};

//-----------------------------------------------------------------------------

TextFile::TextFile(const char* filename)
{
	m_this = new TextFileImpl(filename);
}

TextFile::~TextFile()
{
	delete m_this;
}

bool TextFile::readString(char* buf, int size)
{
	return m_this->readString(buf, size);
}

void TextFile::skipLine()
{
	m_this->skipLine();
}

long TextFile::readHex()
{
	return m_this->readHex();
}

bool TextFile::skipWhitespace()
{
	return m_this->skipWhitespace();
}

TextFile::ErrorType TextFile::error() const
{
	return m_this->err;
}

bool TextFile::readChar(char* ch)
{
	return m_this->readChar(ch);
}

bool TextFile::peekChar(char* ch)
{
	return m_this->peekChar(ch);
}

bool TextFile::eof() const
{
	return m_this->eof();
}

int TextFile::line() const
{
	return m_this->line;
}
